<?php declare(strict_types=1);


namespace App\Lib\RpcRequest;

use App\Lib\Consul\Service;
use App\Lib\Consul\ServiceHelper;
use App\Lib\Consul\ServiceInfo;
use App\Lib\RpcClient\RpcClient;
use App\Lib\RpcClient\RpcResult;
use App\Lib\Consul\ServiceSelector;
use Swoft\Bean\Annotation\Mapping\Bean;
use Swoft\Bean\Annotation\Mapping\Inject;
use Swoft\Consul\Exception\ClientException;
use Swoft\Consul\Exception\ServerException;
use function GuzzleHttp\Psr7\str;

/**
 * rcp request client
 * Class RpcRequest
 * @package App\Lib\RpcRequest
 * @Bean()
 */
class RpcRequest
{
    public const RPC_EOL = "\r\n\r\n";

    public const RPC_LIB = "App\\Rpc\Lib\\";

    /**
     * @Inject()
     * @var ServiceSelector
     */
    private $ServiceSelector;

    /**
     * @Inject()
     * @var ServiceHelper
     */
    private $ServiceHelper;

    /**
     * 请求Host:port
     * @var string
     */
    private $host;

    /**
     * requestMethod calculator/add
     * @var string
     */
    private $method;

    /**
     * params ['params']
     * @var array
     */
    private $params = [[]];

    /**
     * @var string
     */
    private $rpcType = 'swoft';

    /**
     * @var string
     */
    private $version = '1.0';

    /**
     * @var string
     */
    private $class;

    /**
     * 服务前缀默认为公共
     * @var string
     */
    private $servicePrefix = 'Common';

    /**
     * 服务名称
     * @var string
     */
    private $serviceName = '';

    /**
     * 获取consul全名
     * @var string
     */
    private $fullName = '';

    /**
     * @return string
     * @throws ClientException
     * @throws ServerException
     */
    public function getHost(): string
    {
        //为了获取参数
        $service = $this->selectService();
        $this->host = $service->getHost();
        return $this->host;
    }

    /**
     * @param string $host
     * @return RpcRequest
     */
    public function setHost(string $host)
    {
        $this->host = $host;
        return $this;
    }

    /**
     * @return string
     */
    public function getMethod(): string
    {
        return $this->method;
    }

    /**
     * @param string $method
     * @return RpcRequest
     */
    public function setMethod(string $method)
    {
        $this->method = $method;
        return $this;
    }

    /**
     * @return array
     */
    public function getParams(): array
    {
        return $this->params;
    }

    /**
     * @param array $params
     * @return RpcRequest
     */
    public function setParams(array $params=[])
    {
        $this->params = $params;
        //if是有序数组
        //else键值数组
        return $this;
    }

    /**
     * @return string
     */
    public function getVersion(): string
    {
        return $this->version;
    }

    /**
     * @param string $version
     * @return RpcRequest
     */
    public function setVersion(string $version): RpcRequest
    {
        $this->version = $version;
        return $this;
    }

    /**
     * @return string
     */
    public function getClass(): string
    {
        return $this->class;
    }

    /**
     * @param string $class
     * @return RpcRequest
     */
    public function setClass(string $class): RpcRequest
    {
        $this->class = $class;
        return $this;
    }


    /**
     * @return string
     */
    public function getServicePrefix(): string
    {
        return $this->servicePrefix;
    }

    /**
     * 设置服务前缀
     * @param string $servicePrefix
     * @return RpcRequest
     */
    public function setServicePrefix(string $servicePrefix): RpcRequest
    {
        $this->servicePrefix = $servicePrefix;
        return $this;
    }

    /**
     * @return string
     */
    public function getServiceName(): string
    {
        return $this->serviceName;
    }

    /**
     * @param string $serviceName
     * @return RpcRequest
     */
    public function setServiceName(string $serviceName): RpcRequest
    {
        $this->serviceName = $serviceName;
        return $this;
    }

    /**
     * @return string
     */
    public function getFullName(): string
    {
        return $this->fullName;
    }

    /**
     * @param string $fullName
     * @return RpcRequest
     */
    public function setFullName(string $fullName): RpcRequest
    {
        $this->fullName = $fullName;
        return $this;
    }

    /**
     * 根据serviceInfo (如：Common.User.UserInterface.find) 获取RpcClient
     * @param string $serviceInfo
     * @return RpcRequest
     */
    public function getServiceByServiceInfo (string $serviceInfo)
    {
        // 克隆当前类 防止Bean 注入后 出现类的相互污染
        $service = clone $this;
        // 实例化ServiceInfo 类
        $serviceInfo = new ServiceInfo($serviceInfo);
        // 如果实例化后ServiceInfo 类不可用则返回 service
        if (!$serviceInfo->useful()) {
            return $service;
        }
        // 设置Prefix
        $service->setServicePrefix( $serviceInfo->getPrefix() );
        // 设置 serviceName
        $service->setServiceName( $serviceInfo->getServiceName() );
        // 设置 服务类
        $service->setClass( $serviceInfo->getServer() );
        // 设置 服务方法
        $service->setMethod( $serviceInfo->getAction() );
        // 设置筛选 的全名
        $service->setFullName( $serviceInfo->getFullName() );

        return $service;
    }

    /**
     * 在Consul中获取服务
     * @return Service|bool
     * @throws \Swoft\Consul\Exception\ClientException
     * @throws \Swoft\Consul\Exception\ServerException
     */
    protected function selectService () {
        // 如果没有服务名称返回false
        if (empty($this->serviceName)) {
            return false;
        }

        //筛选服务,local走原来的consul,其他自定义
        if(env('APP_ENV', 'local') === 'local'){
            $services = $this->ServiceSelector->services($this->fullName);
            /** @var Service $service */
            $service = $this->ServiceHelper->random($services);
        }else{
            //生成oos-test
            $service['Meta']['SERVICE_ADDRESS'] = getSocketDns($this->serviceName);
            $service['Meta']['SERVICE_PORT'] = config('service.rpc_service_port', 18307);
            if(strtolower($this->servicePrefix) == 'hyperf'){
                $service['Meta']['SERVICE_PORT'] = config('service.rpc_hyperf_port', 9503);
            }
            $service['Meta']['LIMITER_RATE'] = config('service.limiter_rate', 10000);
            $service['Meta']['LIMITER_MAX'] = config('service.limiter_max', 10000);
            $service = new Service($service);
        }

        return $service;
    }

    /**
     * @param bool $origin
     * @return bool|mixed
     * @throws \Swoft\Consul\Exception\ClientException
     * @throws \Swoft\Consul\Exception\ServerException
     * @throws \Exception
     */
    public function query(bool $origin = false)
    {
        // 获取服务
        $service = $this->selectService();
        $this->host = $service->getHost();

        if (empty($service)) {
            return false;
        }
        // 如果获取到的服务不可用则返回false
        if (!$service->useful()) {
            return false;
        }
        return  $this->request($origin);
    }


    /**
     * 请求方法
     * @param bool $origin
     * @return array|mixed|string
     * @throws \Exception
     */
    public function request(bool $origin = false) {
        $host   = $this->host;
        $method = $this->method;
        $param  = $this->params;
        $ext    = [];
        $version = $this->version;
        $class = $this->class;

        /****************************************start connect*********************************************/
        $fp = stream_socket_client($host, $errno, $errstr);
        if (!$fp) {
            throw new \Exception("stream_socket_client fail errno={$errno} errstr={$errstr}");
        }

        $time_out = config('service.time_out', 60);
        $start_time = time();

        stream_set_blocking($fp, true);
        stream_set_timeout($fp, (int) $time_out);

        if(strtolower($this->servicePrefix) == 'hyperf'){
            /**
             * this is the hyperf standard
             */
            $req = [
                "jsonrpc" => '2.0',
//                "method" => sprintf("%s", strtolower($this->servicePrefix) .'_'. strtolower($class) .'/'. $method),
                "method" => sprintf("%s",  strtolower($class) .'/'. $method),
                'params' => $param?[$param]:[],
                'id' => '',
                'context' => $ext,
            ];
            var_dump($req);
        }else{
            /**
             * this is the swoft standard
             */
            $req = [
                "jsonrpc" => '2.0',
                "method" => sprintf("%s::%s::%s", $version, self::RPC_LIB . $class , $method),
                'params' => $param,
                'id' => '',
                'ext' => $ext,
            ];
        }
//        var_dump($req, 222);
        $data = json_encode($req) . self::RPC_EOL;
        fwrite($fp, $data);

        $result = '';
        while (!feof($fp)) {
            $tmp = stream_socket_recvfrom($fp, 1024);
            $diff = time() - $start_time;
            $status = stream_get_meta_data($fp);
            if ($diff > $time_out || $status['timed_out']) {
                break;
            }

            if (($pos = strpos($result.$tmp, self::RPC_EOL)) !== false) {
                $result .= substr($tmp, 0, $pos);
                break;
            } else {
                if(($pos = strpos($tmp, self::RPC_EOL)) !== false){
                    break;
                }
                $result .= $tmp;
            }
        }

        fclose($fp);
        if($origin){
            return $result;
        }else{
            //这里有两种情况
            $retData = json_decode($result, true);
            if(strtolower($this->servicePrefix) == 'hyperf'){
                return ($retData['error']??[])?:($retData['result']??[]);
            }else{
                return ($retData['error']??[])?:($retData??[]);
            }
        }
    }
}
